package wordEvo.server;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.List;
import java.util.Vector;

import whipstaff.tools.IOHelper;
import whipstaff.tools.OutputLog;
import whipstaff.wordevo.data.Result;

/**
 * @author Jim Buck, Whipstaff Productions
 * @since August 10, 2011
 * @updated August 30, 2011
 */
public class EvoServer
{
	private final int maxConnections = 32;
	private int port = 8888;
	private ServerSocket listener;
	private Socket client;
	
	private OutputLog log;
	
	private List<RecieveThread> threadList;
	private List<Result> resultList;
	
	public EvoServer() {
		this.threadList = new Vector<RecieveThread>();
		this.resultList = new Vector<Result>();
		this.log = new OutputLog("./logs", "serverlog", true);
	}
	
	public void begin() {

		/* * * * * * * * * * * * * * * * * * * * * * * * *
		 * Steps:
		 * 	- Initiate Server.
		 *  - Wait for connections.
		 *  - If connected, dump Results onto filesystem (to be loaded later).
		 * * * * * * * * * * * * * * * * * * * * * * * * */
		
		/* * * * * * * * * * * * * * * * * * * * * * * * *
		 * Later:
		 * 	- Check for and load requests.
		 *  - DataBase backups (and recoveries).
		 *  - Send Requests to Clients.
		 *  - Statistics sheet/easy output.
		 * * * * * * * * * * * * * * * * * * * * * * * * */
		
		initServer();
		run();
		shutDown();
	}

	private void initServer() {	
		this.log.println("Server initiating...");
		try {
			this.listener = new ServerSocket(this.port);
			this.listener.setSoTimeout(3000);
			this.client = null;
		} catch (IOException e) {
			this.log.printerr("Could not listen on port: " + this.port);
			System.exit(1);
		}
		this.log.println("Server initiated.");
	}

	private void run() {
		// Wait for connection, then create new thread.
		// Periodically clean up threads.
		this.log.println("Server listening on " + this.port + "...");
		while (true) {
			updateThreads();
			saveResults();
		}
		
	}
	
	private void updateThreads() {
		if (this.threadList.size() < this.maxConnections) {
			try {
				cleanThreads();
				this.client = this.listener.accept();
				if(this.client != null)
				{
					RecieveThread rThread = new RecieveThread(this);
					this.threadList.add(rThread);
					this.log.println("New Connection: " + rThread.toString());
					rThread.start();
					this.client = null;
				}
			} catch (SocketTimeoutException e) {
				//System.out.println("Listener timeout...");
			} catch (IOException e) {
				this.log.printerr("ERROR: There was a problem accepting the connection.");
				e.printStackTrace();
				System.exit(1);
			}
		}
	}
	
	private void saveResults() {
		for(int i = 0; i < this.resultList.size(); i++) {
			saveToFile(this.resultList.get(i));
			this.resultList.remove(i);
		}
	}
	
	private void saveToFile(Result res) {
		File outFile = null;
		
		try {
			outFile = new File("./results", IOHelper.getNewFileName("./results", "result", ".res"));
			IOHelper.saveToFile(res, outFile);
		} catch (FileNotFoundException e) {
			this.log.printerr(e.getMessage());
			this.log.printerr("There was an error creating the file. Moving on...");
		} catch (IOException e) {
			this.log.printerr(e.getMessage() + ". (" + outFile.getAbsolutePath() + ")");
			e.printStackTrace();
			this.log.printerr("There was an error saving to file. Moving on...");
		}
	}

	private void shutDown() {
		try {
			this.listener.close();
		} catch (IOException e) {
			this.log.printerr("Failed to close ServerSocket on port: " + this.port);
			System.exit(1);
		}
		
		this.log.println("Server Complete!");
		System.exit(0);
	}
	
	private void cleanThreads() {
		for(int i = 0; i < this.threadList.size(); i++) {
			if(!this.threadList.get(i).isAlive()) {
				this.threadList.remove(i);
			}
		}
	}
	
	public Socket getSocket() {
		return this.client;
	}
	
	public void submitResult(Result r) {
		this.resultList.add(r);
	}
}
